/******************************************************************************
*	device/mouse.c
*	 by Steve White
*
*	A light weight implementation of the USB protocol stack fit for a simple
*	driver.
*
*	device/mouse.c contains code relating to USB hid mice. The
*	driver maintains a list of the mice on the system, and allows the
*	operating system to retrieve the status of each one separately. It is coded
*	a little awkwardly on purpose to make OS development more fun!
******************************************************************************/
#include <device/hid/hid.h>
#include <device/hid/mouse.h>
#include <device/hid/report.h>
#include <platform/platform.h>
#include <types.h>
#include <usbd/device.h>
#include <usbd/usbd.h>

#define  MouseMaxMice  (4)  // Must be a multiple of 4

UsbDevice  *mice[MouseMaxMice];

u32        mouseCount ALIGN4            = 0;
u32        mouseAddresses[MouseMaxMice] = { 0, 0, 0, 0 };

//+=============================================================================
// Func: MouseLoad
// Desc: Initialise the mouse details
// Args: -none-
// Retn: -none-
// Note:
// Auth: Chadderz; Steve White; (BlueChip)
//==============================================================================
void  MouseLoad (void)
{
	u32  i;

	LOG_DEBUG("CSUD: Mouse driver version 0.1\n");

	mouseCount = 0;
	for (i = 0;  i < MouseMaxMice;  i++) {
		mouseAddresses[i] = 0;
		mice[i]           = NULL;
	}
	HidUsageAttach[DesktopMouse] = MouseAttach;

	return;
}

//+=============================================================================
// Func: MouseIndex
// Desc: Find the index of the mouse from its address
// Args: u32 address .. Address of mouse
// Retn: u32 .. Index in to mouseAddresses -or- 0xFFFFFFFF if not found
// Note: -
// Auth: Chadderz; Steve White; (BlueChip)
//==============================================================================
u32  MouseIndex (u32 address)
{
	u32  i;

	if (address == 0) return 0xFFFFFFFF;

	for (i = 0;  i < mouseCount;  i++)
		if (mouseAddresses[i] == address)  return i ;

	return 0xFFFFFFFF;
}

//+=============================================================================
// Func: MouseGetAddress
// Desc: Returns the device-address of the n'th connected mouse.
// Args: u32 index .. Index in to mouseAddresses
// Retn: u32 .. Address of mouse -or- 0 if mouse not found
// Note: Mice that are connected are stored in an array.
//         This function retrieves the n'th item from that array.
//     : ==DEPRECATED==
// Auth: Chadderz; Steve White; (BlueChip)
//==============================================================================
/* DEPRECATED
u32  MouseGetAddress (u32 index)
{
	u32  i;

	if (index > mouseCount)  return 0 ;

	for (i = 0;  (index >= 0) && (i < MouseMaxMice);  i++)
		if (mouseAddresses[i] != 0)
			if (index-- == 0)  return mouseAddresses[i] ;

	return 0;
}
*/

//+=============================================================================
// Func: MouseDetached
// Desc:
// Args: UsbDevice* device ..
// Retn: -none-
// Note:
// Auth: Chadderz; Steve White; (BlueChip)
//==============================================================================
void  MouseDetached (UsbDevice* device)
{
	MouseDevice  *data;

	data = (MouseDevice*)((HidDevice*)device->DriverData)->DriverData;
	if (data != NULL) {
		if (mouseAddresses[data->Index] == device->Number) {
			mouseAddresses[data->Index] = 0;
			mice[data->Index]           = NULL;
			mouseCount--;
		}
	}
}

//+=============================================================================
// Func: MouseDeallocate
// Desc: Deallocate mouse from USB Stack
// Args: UsbDevice* device ..
// Retn: -none-
// Note: -
// Auth: Chadderz; Steve White; (BlueChip)
//==============================================================================
void  MouseDeallocate (UsbDevice* device)
{
	HidDevice  *hdr = (HidDevice*)device->DriverData;

	if (hdr->DriverData != NULL) {
		MemoryDeallocate(hdr->DriverData);
		hdr->DriverData = NULL;
	}
	hdr->HidDeallocate = NULL;
	hdr->HidDetached   = NULL;

	return;
}

//+=============================================================================
// Func: MouseAttach
// Desc: Enumerates a device as a mouse.
// Args: UsbDevice* device    ..
//     : u32        interface ..
// Retn: Result .. A csud 'Result' code
// Note: Checks a device already checked by HidAttach to see if it appears to be
//         a mouse. If so, build up necessary information to enable the
//         mouse methods.
// Auth: Chadderz; Steve White; (BlueChip)
//==============================================================================
Result  MouseAttach (UsbDevice *device, u32 interface)
{
	u32              mouse, i;
	HidDevice        *hidData;
	MouseDevice      *data;
	HidParserResult  *parse;

	// Sanity check the driver build
	//! should ba an assert()
	if ((MouseMaxMice & 3) != 0) {
		LOG("MOUSE: Warning! MouseMaxMice not a multiple of 4. "
		    "The driver wasn't built for this!\n");
	}

	// Make sure we are not trying to connect too many mice
	if (mouseCount == MouseMaxMice) {
		LOGF("MOUSE: %s not connected. Too many mice connected (%d/%d). "
		     "Change MouseMaxMice in device.mouse.c to allow more.\n",
		     UsbGetDescription(device), mouseCount, MouseMaxMice);
		return ErrorIncompatible;
	}

	// Check we have a HID device
	hidData = (HidDevice*)device->DriverData;
	if (hidData->Header.DeviceDriver != DeviceDriverHid) {
		LOGF("MOUSE: %s isn't a HID device. "
		     "The mouse driver is built upon the HID driver.\n",
		     UsbGetDescription(device));
		return ErrorIncompatible;
	}

	// Check the device reports itself as being a mouse
	parse = hidData->ParserResult;
	if ((parse->Application.Page    != GenericDesktopControl) ||
		(parse->Application.Desktop != DesktopMouse)) {
		LOGF("MOUSE: %s doesn't seem to be a mouse...\n", UsbGetDescription(device));
		return ErrorIncompatible;
	}

	//?
	if (parse->ReportCount < 1) {
		LOGF("MOUSE: %s doesn't have enough outputs to be a mouse.\n",
		     UsbGetDescription(device));
		return ErrorIncompatible;
	}

	//?
	hidData->HidDetached   = MouseDetached;
	hidData->HidDeallocate = MouseDeallocate;
	hidData->DriverData    = MemoryAllocate(sizeof(MouseDevice));
	if (hidData->DriverData == NULL) {
		LOGF("MOUSE: Not enough memory to allocate mouse %s.\n",
		     UsbGetDescription(device));
		return ErrorMemory;
	}

	//?
	data                      = (MouseDevice*)hidData->DriverData;
	data->Header.DeviceDriver = DeviceDriverMouse;
	data->Header.DataSize     = sizeof(MouseDevice);
	data->Index               = 0xFFFFFFFF;

	// Try and find a free slot for this mouse
	mouse = 0xFFFFFFFF;
	for (i = 0;  i < MouseMaxMice;  i++) {
		if (mouseAddresses[i] == 0) {
			data->Index       = i;
			mouse       = i;
			mouseAddresses[i] = device->Number;
			mouseCount++;
			break;
		}
	}
	// If we failed to find a free slot, we have a problem
	if (mouse == 0xffffffff) {
		//! I do not believe this error message to either informative or correct
		//! A slot was not found, so the mouseCount was never increased.
		LOG("MOUSE: PANIC! Driver in inconsistent state! MouseCount is inaccurate.\n");
		MouseDeallocate(device);
		return ErrorGeneral;
	}
	mice[mouse] = device;

	for (i = 0;  i < parse->ReportCount;  i++) {
		if ((parse->Report[i]->Type == Input) /*&& (data->MouseReport == NULL)*/) {
			LOG_DEBUGF("MOUSE: Output report %d. %d fields.\n",
			           i, parse->Report[i]->FieldCount);
			data->MouseReport = parse->Report[i];
		}
	}

	data->mouseX      = 0;
	data->mouseY      = 0;
	data->wheel       = 0;
	data->buttonState = 0;

	LOG_DEBUGF("MOUSE: New mouse assigned %d!\n", device->Number);

	return OK;
}

//+=============================================================================
// Func: MouseCount
// Desc: Returns the number of mice connected to the system.
// Args: -none-
// Retn: u32 .. The number of mice connected to the system
// Note: -
// Auth: Chadderz; Steve White; (BlueChip)
//==============================================================================
u32  MouseCount (void)
{
	return mouseCount;
}

//+=============================================================================
// Func: MousePoll
// Desc: Checks the specified mouse.
// Args: u32 mouseAddress ..
// Retn: Result .. A csud 'Result' code [1]
// Note: Reads back the report of the specified mouse
//         and parses it into the internal fields.
//     : The 'internal fields' can be accessed with MouseGet??? methods
//     : [1] Returns ErrorDisconnected if an invalid mouseAddress is specified
// Auth: Steve White; (BlueChip)
//==============================================================================
Result  MousePoll (u32 mouseAddress)
{
	u32              mouse;
	Result           result;
	HidParserReport  *MouseReport;
	MouseDevice      *data;

	mouse = MouseIndex(mouseAddress);
	if (mouse == 0xFFFFFFFF)  return ErrorDisconnected ;

	data = (MouseDevice*)((HidDevice*)mice[mouse]->DriverData)->DriverData;

	result = HidReadDevice(mice[mouse], data->MouseReport->Index);
	if (result != OK) {
		if (result != ErrorDisconnected)
			LOGF("MOUSE: Could not get mouse report from %s.\n",
			     UsbGetDescription(mice[mouse]));
		return result;
	}

	MouseReport = data->MouseReport;
	if (MouseReport->Type == Input) {
		//! I'm sure I should be using HidGetFieldValue()
		//! But this was so terribly easy.
		u8  *ReportBuffer  = MouseReport->ReportBuffer;
		data->buttonState  = (u8)ReportBuffer[0];
		data->mouseX      += (s8)ReportBuffer[1];
		data->mouseY      += (s8)ReportBuffer[2];
		data->wheel       += (s8)ReportBuffer[3];

		if (data->mouseX < 0)  data->mouseX = 0 ;
		if (data->mouseY < 0)  data->mouseY = 0 ;
	}

	return OK;
}

//+=============================================================================
// Func: MouseData
// Desc:
// Args: u32 mouseAddress ..
// Retn: MouseDevice* ..
// Note: This "static inline" function is to affect code simplicity.
// Auth: BlueChip
//==============================================================================
static inline  MouseDevice*  MouseData (u32 mouseAddress)
{
	u32          mouse = MouseIndex(mouseAddress);

	if (mouse == 0xFFFFFFFF)  return NULL ;

	return (MouseDevice*)((HidDevice*)mice[mouse]->DriverData)->DriverData;
}

//+=============================================================================
// Func: MouseGetPositionX
// Desc: Get the current {x} coordinate of the specified mouse.
// Args: u32 mouseAddress ..
// Retn: s16 .. current {x} coordinate of the specified mouse. [1]
// Note: [1] Returns {0} if an invalid mouseAddress is specified
// Auth: Steve White; BlueChip
//==============================================================================
s16  MouseGetPositionX (u32 mouseAddress)
{
	MouseDevice  *data = MouseData(mouseAddress);

	return data ? data->mouseX : 0 ;
}

//+=============================================================================
// Func: MouseGetPositionY
// Desc: Get the current {y} coordinate of the specified mouse.
// Args: u32 mouseAddress ..
// Retn: s16 .. current {y} coordinate of the specified mouse. [1]
// Note: [1] Returns {0} if an invalid mouseAddress is specified
// Auth: Steve White; BlueChip
//==============================================================================
s16  MouseGetPositionY (u32 mouseAddress)
{
	MouseDevice  *data = MouseData(mouseAddress);

	return data ? data->mouseY : 0 ;
}

//+=============================================================================
// Func: MouseGetPosition
// Desc: Get the current {x,y} coordinates of the specified mouse. [1]
// Args: u32 mouseAddress ..
// Retn: u32 .. The current {x,y} coordinates of the specified mouse. [1][2]
// Note: [1] {x}    top-16 bits of the result, Ie. ((result >> 16) & 0xFFFF);
//           {y} bottom-16 bits of the result, Ie. (result & 0xFFFF)
//     : [2] Returns {0,0} if an invalid mouseAddress is specified
//     : For speed conssiderations, this is NOT composed by calling
//         MouseGetPositionX() and MouseGetPositionY()
// Auth: Steve White; BlueChip
//==============================================================================
u32  MouseGetPosition (u32 mouseAddress)
{
	MouseDevice  *data = MouseData(mouseAddress);

	return data ? ((data->mouseX << 16) | (data->mouseY & 0xFFFF)) : 0 ;
}

//+=============================================================================
// Func: MouseGetWheel
// Desc: Get the current wheel-value of the specified mouse.
// Args: u32 mouseAddress ..
// Retn: s16 .. The current wheel-value of the specified mouse. [1]
// Note: [1] Returns 0 if an invalid mouseAddress is specified
// Auth: Steve White; BlueChip
//==============================================================================
s16  MouseGetWheel (u32 mouseAddress)
{
	MouseDevice  *data = MouseData(mouseAddress);

	return data ? data->wheel : 0 ;
}

//+=============================================================================
// Func: MouseGetButtons
// Desc: Get the current button-state of the specified mouse.
// Args: u32 mouseAddress ..
// Retn: u8 .. the current button-state of the specified mouse. [1][2]
// Note: [1] Returns 0 if an invalid mouseAddress is specified
//     : [2] The bits of the return value are: xxxESMRL
//           x=Unused; E=Extra; S=Side; M=Middle, R=Right; L=Left
// Auth: Steve White; BlueChip
//==============================================================================
u8  MouseGetButtons (u32 mouseAddress)
{
	MouseDevice  *data = MouseData(mouseAddress);

	return data ? data->buttonState : 0 ;
}

//+=============================================================================
// Func: MouseGetButtonIsPressed
// Desc: Query if the specified mouse-button is currently being pressed.
// Args: u32               mouseAddress ..
//     : MouseDeviceButton button       .. Button to be queried [1]
// Retn: bool .. true iff pressed; false iff not-pressed [2][3]
// Note: [1] Valid MouseDeviceButton's are: MouseDeviceButton???,
//           where "???" is: Left | Right | Middle | Side | Extra
//     : [2] Returns 'false' if an invalid mouseAddress is specified
//     : [3] Return value is 'undefined' for Illegal 'button' values
//     : Reads back whether or not a mouse button was pressed on the last
//         successfully received report.
// Auth: Steve White; BlueChip
//==============================================================================
bool  MouseGetButtonIsPressed (u32 mouseAddress, MouseDeviceButton button)
{
	MouseDevice  *data = MouseData(mouseAddress);

	return data ? (data->buttonState & button) : false ;
}
